using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Refly.CodeDom;
using Refly.CodeDom.Statements;
using Refly.CodeDom.Collections;
using Refly.Templates;
using Refly.CodeDom.Expressions;
using System.Text;
using System.Text.RegularExpressions;
using ASOGS.Patterns;
using Reflector.Framework;
using System.ComponentModel;
using Doubler.Data;
using Doubler.Helpers;
using Doubler.Helpers.Graph;

namespace Doubler.Templates.Sneak
{
    public class SneakTemplate : SingleTemplateBase
    {

#region Fields

        private IClassDeclaration _WrapperClass;
        private MethodDeclaration _AttachToEventsMethod;
        private StringCollection _MembersToOmit;
        private MemberList _MembersProcessed;

#endregion
       
#region Properties

        private MemberList MembersProcessed
        {
            get
            {
                if (_MembersProcessed == null)
                    _MembersProcessed = new MemberList();
                return _MembersProcessed;
            }
            set
            {
                _MembersProcessed = value;
            }
        }

        public StringCollection MembersToOmit
        {
            get
            {
                if (_MembersToOmit == null)
                {
                    _MembersToOmit = new StringCollection();
                    _MembersToOmit.Add("GetType");
                }
                return _MembersToOmit;
            }
            set
            {
                _MembersToOmit = value;
            }
        }

        [Browsable(false)]
        public MethodDeclaration AttachToEventsMethod
        {
            get
            {
                return _AttachToEventsMethod;
            }
            set
            {
                _AttachToEventsMethod = value;
            }
        }

        [Browsable(false)]
        public IClassDeclaration WrapperClass
        {
            get
            {
                return _WrapperClass;
            }
            set
            {
                _WrapperClass = value;
            }
        }

        public override string FileName
        {
            get
            {
                return string.Format(base.NameFormat, this.Subject.Name);
            }
        }

        public override string OutputFilePath
        {
            get
            {
                string NamespacePath = Regex.Replace(this.Namespace, @"\.", "/");
                return string.Format("{0}/{1}/{2}.{3}", this.OutputPath, NamespacePath, this.FileName, this.OutputLanguage);
            }
        }
        
#endregion

#region Constructor

        public SneakTemplate()
            : base("", "Wrapper{0}")
        {
            this.Namespace = "Wrapped";
        }

        public SneakTemplate(CodeLanguage language, string outDirectoryPath, Reflector.CodeModel.ITypeDeclaration subject, ReflectorServices reflectorServices)
            : base(language, outDirectoryPath, subject, "Wrapper{0}", reflectorServices)
        {
            this.Namespace = "Wrapped";
        }

#endregion
    
#region Generation

        public override void Generate()
        {
            this.MembersProcessed = null;
            this.Prepare();
            this.GenerateWrapper();
            this.Compile();
        }

        public void GenerateWrapper()
        {
            this.WrapperClass = base.NamespaceDeclaration.AddClass(FileName);
            this.WrapperClass.OutputType = ClassOutputType.Class;
            this.GenerateWrappedContainer();
            this.GenerateAttachToEventsMethod();
            this.GeneratePassThroughs(this.Subject);
            if (this.WrapperClass.Constructors.Count == 0)
            {
                foreach (Reflector.CodeModel.IMethodDeclaration Method in this.Services.Search.FindType("System.Object").Methods)
                {
                    
                    if (MetaHelper.IsNotNestedMember(Method)
                        && !this.MembersToOmit.Contains(Method.Name)
                        && Method.Name.EndsWith(".ctor"))
                    {
                        this.GenerateWrapperConstructor(Method);
                    }
                }
            }
        }

        private void GeneratePassThroughs(Reflector.CodeModel.ITypeDeclaration subject)
        {
            foreach (Reflector.CodeModel.IMethodDeclaration Method in subject.Methods)
            {
                if (MetaHelper.IsNotNestedMember(Method)
                        && !this.MembersToOmit.Contains(Method.Name)
                        && !this.MembersProcessed.Contains(Method))
                {
                    if (Method.Name.EndsWith(".ctor"))
                    {
                        this.GenerateWrapperConstructor(Method);
                    }
                    else
                    {
                        this.GenerateWrapperMethod(Method);
                    }
                    this.MembersProcessed.Add(Method);
                }
            }
            foreach (Reflector.CodeModel.IPropertyDeclaration Property in subject.Properties)
            {
                if (!this.MembersProcessed.Contains(Property))
                {
                    this.GenerateWrapperProperty(Property);
                    this.MembersProcessed.Add(Property);
                }
            }
            foreach (Reflector.CodeModel.IFieldDeclaration Field in subject.Fields)
            {
                if (!this.MembersProcessed.Contains(Field))
                {
                    this.GenerateWrapperField(Field);
                    this.MembersProcessed.Add(Field);
                }
            }
            foreach (Reflector.CodeModel.IEventDeclaration Event in subject.Events)
            {
                if (!this.MembersProcessed.Contains(Event))
                {
                    this.GenerateWrapperEvent(Event);
                    this.MembersProcessed.Add(Event);
                }
            }
			if (subject.BaseType != null && subject.BaseType.Name != "System.Object" && subject.BaseType.Name != "Object")
            {
                this.GeneratePassThroughs(subject.BaseType.Resolve());
            }
        }
        private void GenerateWrappedContainer()
        {
            GenerationHelper.GeneratePropertyAndField(this.WrapperClass, "WrappedSubject", this.Subject, true, true, System.CodeDom.MemberAttributes.Private);
        }

        private void GenerateAttachToEventsMethod()
        {
            this.AttachToEventsMethod = this.WrapperClass.AddMethod("AttachToEvents");
            this.AttachToEventsMethod.Attributes = System.CodeDom.MemberAttributes.Private;
        }

#endregion
        
#region Constructor

        public void GenerateWrapperConstructor(Reflector.CodeModel.IMethodDeclaration constructorInformation)
        {
            IConstructorDeclaration Constructor = this.WrapperClass.AddConstructor();

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in constructorInformation.Parameters)
            {
                ParameterDeclaration Parameter;
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                Parameter = Constructor.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
            }

            Expression WrappedSubject = Expr.This.Prop("WrappedSubject");

            this.WrapperClass.Imports.Add(this.Subject.Namespace);

            AssignStatement NewWrapper = Stm.Assign(WrappedSubject, Expr.New(this.Subject.Name, Constructor.Signature.ArgumentReferences));
            Constructor.Body.Add(NewWrapper);

            MethodReferenceExpression MethodToInvoke = Expr.This.Method(this.AttachToEventsMethod);
            MethodInvokeExpression InvokedMethod = MethodToInvoke.Invoke();
            Constructor.Body.Add(InvokedMethod);
        }
#endregion

#region Wrapper Method Generation

        public void GenerateWrapperMethod(Reflector.CodeModel.IMethodDeclaration methodInformation)
        {
            MethodDeclaration Method = this.WrapperClass.AddMethod(methodInformation.Name);
            Method.Signature.ReturnType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(methodInformation.ReturnType.Type));
            ArrayList TypeArray = new ArrayList();

            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in methodInformation.Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                Method.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
                TypeArray.Add(ParameterInformation.ParameterType);
            }

            try
            {
                if (typeof(object).GetMethod(methodInformation.Name, (Type[])TypeArray.ToArray(typeof(Type))) != null)
                {
                    Method.Attributes = System.CodeDom.MemberAttributes.Override | System.CodeDom.MemberAttributes.Public;
                }
                else if (methodInformation.DeclaringType == typeof(object))
                {
                    Method.Attributes = System.CodeDom.MemberAttributes.New | System.CodeDom.MemberAttributes.Public;
                }
                else if (typeof(object).GetMethod(methodInformation.Name) != null)
                {
                    Method.Attributes = System.CodeDom.MemberAttributes.Overloaded | System.CodeDom.MemberAttributes.Public;
                }
                else
                {
                    Method.Attributes = System.CodeDom.MemberAttributes.Public | System.CodeDom.MemberAttributes.Final;
                }
            }
            catch
            {
            }

            Expression WrappedSubject = Expr.This.Prop("WrappedSubject");
            MethodReferenceExpression MethodToInvoke = WrappedSubject.Method(Method);
            ArrayList TempArray = new ArrayList(Method.Signature.Parameters);
            MethodInvokeExpression InvokedMethod = MethodToInvoke.Invoke((ParameterDeclaration[])TempArray.ToArray(typeof(ParameterDeclaration)));


            if (methodInformation.ReturnType.Type.ToString() != "Void" && methodInformation.ReturnType.Type.ToString() != "System.Void")
            {
                Statement ReturnStatement = Stm.Return(InvokedMethod);
                Method.Body.Add(ReturnStatement);
            }
            else
            {
                Method.Body.Add(InvokedMethod);
            }
        }

#endregion
        
#region Wrapper Property/Field Generation

        public void GenerateWrapperProperty(Reflector.CodeModel.IPropertyDeclaration propertyInformation)
        {
            bool Readable;
            bool Writeable;

            Readable = this.IsReadable(propertyInformation);
            Writeable = this.IsWritable(propertyInformation);

            this.GenerateWrapperProperty((Reflector.CodeModel.ITypeReference)propertyInformation.PropertyType, propertyInformation.Name, Readable, Writeable);
        }

        public void GenerateWrapperField(Reflector.CodeModel.IFieldDeclaration fieldInformation)
        {
            this.GenerateWrapperProperty((Reflector.CodeModel.ITypeReference)fieldInformation.FieldType, fieldInformation.Name, true, true);
        }

#endregion
        
#region Wrapper Event Generation

        public void GenerateWrapperEvent(Reflector.CodeModel.IEventDeclaration eventInformation)
        {
            EventDeclaration Event = this.WrapperClass.AddEvent(new StringTypeDeclaration(MetaHelper.GetFullTypeName(eventInformation.EventType)), eventInformation.Name);

            NativePropertyReferenceExpression WrappedSubject = Expr.This.Prop("WrappedSubject");
            EventReferenceExpression EventRef = WrappedSubject.Event(Event);
            
            MethodDeclaration EventRelayMethod = this.WrapperClass.AddMethod(string.Format("{0}Relay", eventInformation.Name));
            EventRelayMethod.Attributes = System.CodeDom.MemberAttributes.Private;
            foreach (Reflector.CodeModel.IParameterDeclaration ParameterInformation in MetaHelper.GetInvokeMethod(eventInformation).Parameters)
            {
                Refly.CodeDom.StringTypeDeclaration ParameterType = new StringTypeDeclaration(MetaHelper.GetFullTypeName(ParameterInformation.ParameterType));
                EventRelayMethod.Signature.Parameters.Add(ParameterType, ParameterInformation.Name);
            }
            DelegateInvokeExpression InvokedDelegate = Expr.DelegateInvoke(Expr.This.Event(Event), EventRelayMethod.Signature.ArgumentReferences);
            EventRelayMethod.Body.Add(InvokedDelegate);

            DelegateCreateExpression Delegate = Expr.Delegate(((Reflector.CodeModel.ITypeReference)eventInformation.EventType).Name ,Expr.This.Method(EventRelayMethod));
            AttachRemoveEventStatement AttachStm = Stm.Attach(EventRef, Delegate);
            this.AttachToEventsMethod.Body.Add(AttachStm);
        }
#endregion
        
#region Property Field Generation Helpers

        private void GenerateWrapperProperty(Reflector.CodeModel.ITypeReference propertyType, string propertyName, bool Readable, bool Writeable)
        {
            PropertyDeclaration Property = this.WrapperClass.AddProperty(new StringTypeDeclaration(MetaHelper.GetFullTypeName(propertyType)), propertyName);
            Property.Attributes = System.CodeDom.MemberAttributes.Final | System.CodeDom.MemberAttributes.Public;

            Expression WrappedSubject = Expr.This.Prop("WrappedSubject");
            if (Readable)
            {
                Property.Get.Add(Stm.Return(WrappedSubject.Prop(Property.Name)));
            }
            if (Writeable)
            {
                Property.Set.Add(Stm.Assign(WrappedSubject.Prop(Property.Name), Expr.Value));
            }
        }

        public bool IsReadable(Reflector.CodeModel.IPropertyDeclaration propertyInformation)
        {
            return (propertyInformation.GetMethod != null && (((Reflector.CodeModel.IMethodDeclaration)propertyInformation.GetMethod).Visibility & Reflector.CodeModel.MethodVisibility.Public) == Reflector.CodeModel.MethodVisibility.Public);
        }

        public bool IsWritable(Reflector.CodeModel.IPropertyDeclaration propertyInformation)
        {
            return (propertyInformation.SetMethod != null && (((Reflector.CodeModel.IMethodDeclaration)propertyInformation.SetMethod).Visibility & Reflector.CodeModel.MethodVisibility.Public) == Reflector.CodeModel.MethodVisibility.Public);
        }

        private string GetFieldName(PropertyDeclaration Property)
        {
            string FieldName;
            FieldName = string.Format("_{0}", Property.Name);
            return FieldName;
        }

#endregion
        
    }
        
}
